package org.dromara.system.pay;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.dromara.system.util.Md5Util;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.text.DecimalFormat;
import java.util.*;

/**
 * 证书模式APP支付
 */
@Slf4j
@Service
public class WxPayCertService {

    /**
     * 微信支付配置文件
     */
    private static PayConfigUtils config = new PayConfigUtils();
    /**
     * 微信支付接口url
     */
    public static final String URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    /**
     * 退款url
     */
    public static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    /**
     * 付款url
     */
    public static final String WITHDRAW_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";


    /**
     * APP微信证书模式支付
     *
     * @param map     参数
     * @param request 请求
     * @return
     * @throws Exception
     */
    public static Map<String, String> createAppPayment(Map<String, String> map, HttpServletRequest request) throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("appid", config.appid);
        params.put("mch_id", config.mch_id);
        params.put("nonce_str", generateNonceStr());
        params.put("body", "商品描述");
        params.put("out_trade_no", map.get("tradeNo"));
        BigDecimal total = new BigDecimal(map.get("amount")).multiply(new BigDecimal(100));
        DecimalFormat df = new DecimalFormat("0");
        params.put("total_fee", df.format(total));
//        params.put("total_fee", "1");
        params.put("spbill_create_ip", request.getRemoteAddr());
        params.put("notify_url", map.get("notifyUrl"));
        params.put("trade_type", "APP");
        String sign = generateSign(params, config.api_key);
        params.put("sign", sign);
        String xml = getRequestXml(params);
        try (CloseableHttpClient httpClient = createSSLClient(config.cert_path, config.mch_id)) {
            HttpPost httpPost = new HttpPost(URL);
            httpPost.setEntity(new StringEntity(xml, "UTF-8"));
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                Map<String, String> responseMap = doXMLParse(result);
                if ("SUCCESS".equals(responseMap.get("return_code"))) {
                    return buildAppPaymentParams(responseMap.get("prepay_id"));
                } else {
                    throw new RuntimeException("微信支付下单失败: " + responseMap.get("return_msg"));
                }
            }
        }
    }

    private static Map<String, String> buildAppPaymentParams(String prepayId) throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("appid", config.appid);
        params.put("partnerid", config.mch_id);
        params.put("prepayid", prepayId);
        params.put("package", "Sign=WXPay");
        params.put("noncestr", generateNonceStr());
        params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String sign = generateSign(params, config.api_key);
        params.put("sign", sign);
        return params;
    }

    // HTTP客户端（带证书）
    public static CloseableHttpClient createSSLClient(String certPath, String mchId) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (InputStream instream = Files.newInputStream(Paths.get(certPath))) {
            keyStore.load(instream, mchId.toCharArray());
        }
        SSLContext sslContext = SSLContextBuilder.create()
            .loadKeyMaterial(keyStore, mchId.toCharArray())
            .build();
        return HttpClients.custom()
            .setSSLContext(sslContext)
            .build();
    }

    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String generateSign(Map<String, String> params, String apiKey) throws Exception {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);

        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            if (params.get(key) != null && !"".equals(params.get(key)) && !"sign".equals(key)) {
                sb.append(key).append("=").append(params.get(key)).append("&");
            }
        }
        sb.append("key=").append(apiKey);
        return Md5Util.md5Hex(sb.toString()).toUpperCase();
    }

    /**
     * 证书模式支付
     *
     * @param request
     * @return
     */
    public static SortedMap<String, Object> WxPublicPay(Map<String, String> params, HttpServletRequest request) {
        Map<String, String> map = weixinPrePay(params, request);
        SortedMap<String, Object> lastSort = new TreeMap<>();
        //小程序参数如下
//        lastSort.put("appId", config.appid);
//        lastSort.put("timeStamp", System.currentTimeMillis() / 1000);
//        lastSort.put("nonceStr", getRandomString(32));
//        lastSort.put("package", "prepay_id=" + map.get("prepay_id"));
//        lastSort.put("signType", "MD5");
//        String sign = PayCommonService.createSign(lastSort);
//        lastSort.put("paySign", sign);
        //app参数
        lastSort.put("appid", config.appid);
        lastSort.put("partnerid", config.mch_id);
        lastSort.put("prepayid", map.get("prepay_id"));
        lastSort.put("package", "Sign=WXPay");
        lastSort.put("noncestr", getRandomString(32));
        lastSort.put("timestamp", System.currentTimeMillis() / 1000);
        String sign = createSign(lastSort);
        lastSort.put("sign", sign);
        return lastSort;
    }

    /**
     * 微信APP支付签名
     *
     * @param paramsMap 参数
     * @param request   请求request
     * @return
     */
    private static Map<String, String> weixinPrePay(Map<String, String> paramsMap, HttpServletRequest request) {
        String body = "会有";
        SortedMap<String, Object> params = new TreeMap<>();
        params.put("appid", config.appid);
        params.put("mch_id", config.mch_id);//商户号
        params.put("nonce_str", getRandomString(32));//随机字符串
        params.put("body", body);//商品描述
        params.put("attach", paramsMap.get("attach"));//附加数据 非必填
        params.put("out_trade_no", paramsMap.get("tradeNo"));//商户订单号
        params.put("fee_type", "CNY");//标价币种 非必填
        BigDecimal total = new BigDecimal(paramsMap.get("amount")).multiply(new BigDecimal(100));
        DecimalFormat df = new DecimalFormat("0");
        params.put("total_fee", df.format(total));//标价金额
        params.put("spbill_create_ip", request.getRemoteAddr());//终端IP
        params.put("notify_url", paramsMap.get("notifyUrl"));//通知地址
        params.put("trade_type", "APP");//交易类型
        params.put("sign_type", "MD5");
        String sign = createSign(params);
        params.put("sign", sign);
//        String requestXML = getRequestXml(params);
        Map<String, String> map = new HashMap<>();
        try {
//            CloseableHttpResponse response = httpPost(URL, requestXML, false);
//            String respondXml = EntityUtils.toString(response.getEntity(), "utf-8");
//            map = doXMLParse(respondXml);
        } catch (Exception e) {
            log.info("微信支付签名失败" + e.getMessage());
        }
        return map;
    }

    //随机字符串生成
    private static String getRandomString(int length) { //length表示生成字符串的长度
        String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    //请求xml组装
    private static String getRequestXml(Map<String, String> parameters) {
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
                sb.append("<").append(key).append(">").append("<![CDATA[").append(value).append("]]></").append(key).append(">");
            } else {
                sb.append("<").append(key).append(">").append(value).append("</").append(key).append(">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

    //生成签名
    private static String createSign(SortedMap<String, Object> parameters) {
        StringBuilder sb = new StringBuilder();
        Set es = parameters.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v)
                && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k).append("=").append(v).append("&");
            }
        }
        sb.append("key=").append(config.api_key);
        System.out.println("签名结果==" + sb.toString());
        return Md5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
    }

    /**
     * 验证回调签名
     *
     * @param map
     * @return
     */
    public static boolean isTenpaySign(Map<String, String> map) {
        String charset = "utf-8";
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            System.out.println("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
        //过滤空 设置 TreeMap
        SortedMap<String, String> packageParams = new TreeMap<>();
        for (String parameter : map.keySet()) {
            String parameterValue = map.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        StringBuilder sb = new StringBuilder();
        Set es = packageParams.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k) && null != v && !"".equals(v)) {
                sb.append(k).append("=").append(v).append("&");
            }
        }
        sb.append("key=").append(config.api_key);
        //将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        //算出签名
        String tobesign = sb.toString();
        String resultSign = Md5Util.MD5Encode(tobesign, "utf-8").toUpperCase();
        String tenpaySign = packageParams.get("sign").toUpperCase();
        return tenpaySign.equals(resultSign);
    }

    //xml解析
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        for (Object aList : list) {
            Element e = (Element) aList;
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }

    private static String getChildrenText(List children) {
        StringBuilder sb = new StringBuilder();
        if (!children.isEmpty()) {
            for (Object aChildren : children) {
                Element e = (Element) aChildren;
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<").append(name).append(">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</").append(name).append(">");
            }
        }
        return sb.toString();
    }
}

